home *** CD-ROM | disk | FTP | other *** search
- #! /bin/sh
- # This is a shell archive, meaning:
- # 1. Remove everything above the #! /bin/sh line.
- # 2. Save the resulting text in a file.
- # 3. Execute the file with /bin/sh (not csh) to create:
- # arcana.pas
- # brdhndlr.pas
- # butler.pas
- # fumiko.doc
- # fumiko.pas
- # globlctv.pas
- # groupmgr.pas
- # sequence.pas
- # tactics.pas
- # trickle.fum
- # This archive created: Mon Aug 10 20:12:28 1992
- export PATH; PATH=/bin:/usr/bin:$PATH
- if test -f 'arcana.pas'
- then
- echo shar: "will not over-write existing file 'arcana.pas'"
- else
- cat << \SHAR_EOF > 'arcana.pas'
- UNIT Arcana;
- {Silly but necessary routines for Fumiko}
-
-
- INTERFACE
-
-
- USES
- Dos, GloblCTV;
-
-
- CONST
- point_interpretation_size = sizeof (point_interpretation);
-
-
- FUNCTION Plenty_Of_Time (var turn: integer): boolean;
- {Returns true if the game is running on or ahead of schedule}
-
-
- FUNCTION On_Board (var x, y: byte): boolean;
- {Returns true if the coordinates name a point on the board}
-
-
- FUNCTION Screen_Char (var point: color_type): char;
- {Returns the character to be printed in the board diagram for the point}
-
-
- FUNCTION Opposite (var point: color_type): color_type;
- {Black_stone and white_stone are opposites}
-
-
- FUNCTION On_Edge (var x: byte): boolean;
- {Returns true if the coordinate is at the edge of the board}
-
-
- FUNCTION Line (var x: byte): byte;
- {Returns the distance to the nearest edge}
-
-
- FUNCTION Equal (var a, b; size: word): boolean;
- {Compares two records. From TP60 Programmer's Guide, p. 107}
-
-
- PROCEDURE Compress (var removed: byte; var board: board_type);
- {Puts the last item in the list into the hole}
-
-
- PROCEDURE Find_Member (var group: group_type; var member: location_type);
- {Sets member to the coordinates of a member of the group}
-
-
- FUNCTION Highest_Rated (var rating: integer_board): location_list;
- {Generates a list of the search_breadth highest rated locations, with}
- {the highest-rated ones at the bottom}
-
-
- PROCEDURE Clean_Out_Location_List (var list: location_list);
- {Deallocates the list}
-
-
- FUNCTION Count_Groups (var board: board_type): byte;
- {Debugging function--count the number of groups}
-
-
- IMPLEMENTATION
-
-
- TYPE
- bytes = array [0..maxint] of byte; {Used in equal}
-
-
- FUNCTION Plenty_Of_Time (var turn: integer): boolean;
- {Returns true if the game is running on or ahead of schedule}
- Begin {Plenty Of Time}
- plenty_of_time := (100 * (125 - (turn div 2)) div time_left) < (12500 div total_time)
- End; {Plenty Of Time}
-
-
- FUNCTION On_Board (var x, y: byte): boolean;
- {Returns true if the coordinates name a point on the board}
- Begin {On Board}
- on_board := (x > 0) and (x <= board_size) and (y > 0) and (y <= board_size)
- End; {On Board}
-
-
- FUNCTION Screen_Char (var point: color_type): char;
- {Returns the character to be printed in the board diagram for the point}
- Begin {Screen Char}
- Case point of
- black_stone: screen_char := #1;
- white_stone: screen_char := #2;
- empty: screen_char := #250;
- off_board: screen_char := '?'
- End
- End; {Screen Char}
-
-
- FUNCTION Opposite (var point: color_type): color_type;
- {black_stone and white_stone are opposites}
- Begin {Opposite}
- If point = black_stone
- Then opposite := white_stone
- Else opposite := black_stone
- End; {Opposite}
-
-
- FUNCTION On_Edge (var x: byte): boolean;
- {Returns true if the coordinate is at the edge of the board}
- Begin {On Edge}
- on_edge := (x = 1) or (x = board_size)
- End; {On Edge}
-
-
- FUNCTION Line (var x: byte): byte;
- {Returns the distance to the nearest edge}
- Begin {Line}
- If x > (board_size + 1 - x)
- Then line := board_size + 1 - x
- Else line := x
- End; {Line}
-
-
- FUNCTION Equal (var a, b; size: word): boolean;
- {Compares two records. From TP60 Programmer's Guide, p. 107}
- Var
- n: integer;
- Begin {Equal}
- n := 0;
- While (n < size) and (bytes (a) [n] = bytes (b) [n]) do
- inc (n);
- equal := n = size
- End; {Equal}
-
-
- PROCEDURE Compress (var removed: byte; var board: board_type);
- {Puts the last item in the list into the hole}
- Var
- last_group, x, y: byte;
- buffer: group_pointer;
- Begin {Compress}
- last_group := removed;
- While board.groups [last_group + 1]^.size <> 0 do
- Inc (last_group);
- If last_group <> removed
- Then Begin
- For x := 1 to board_size do
- For y := 1 to board_size do
- With board.groups [last_group]^.interpretation [x, y] do
- If member
- Then board.data [x, y].group := removed;
- buffer := board.groups [removed];
- board.groups [removed] := board.groups [last_group];
- board.groups [last_group] := buffer;
- board.groups [last_group]^.size := 0
- End
- Else board.groups [removed]^.size := 0
- End; {Compress}
-
-
- PROCEDURE Find_Member (var group: group_type; var member: location_type);
- {Sets member to the coordinates of a member of the group}
- Var
- x, y: byte;
- Begin {Find Member}
- x := 1;
- y := 1;
- member.x := 0;
- Repeat
- If group.interpretation [x, y].member
- Then Begin
- member.x := x;
- member.y := y
- End;
- Inc (x);
- If x > board_size
- Then Begin
- x := 1;
- Inc (y)
- End
- Until member.x <> 0
- End; {Find Member}
-
-
- PROCEDURE Swap_Values (var node_1, node_2: location_list);
- {Swaps the location and value fields of the two nodes. LOCAL}
- Var
- location_buffer: location_type;
- value_buffer: integer;
- Begin {Swap Values}
- location_buffer := node_1^.location;
- value_buffer := node_1^.value;
- node_1^.location := node_2^.location;
- node_1^.value := node_2^.value;
- node_2^.location := location_buffer;
- node_2^.value := value_buffer
- End; {Swap Values}
-
-
- PROCEDURE Filter_Down (var list: location_list; var x, y: byte; var value: integer);
- {The ugly part of highest_rated. Moves the new item along the list}
- {until it meets a higher-value item. LOCAL}
- Var
- this, that: location_list;
- swapped: boolean;
- Begin {Filter Down}
- New (this);
- this^.location.x := x;
- this^.location.y := y;
- this^.value := value;
- this^.next := nil;
- that := list;
- Repeat
- If this^.value > that^.value
- Then Begin
- Swap_Values (this, that);
- If this^.next = nil
- Then Dispose (this);
- this := that;
- that := that^.next;
- swapped := true
- End
- Else swapped := false
- Until (that = nil) or (not swapped);
- If that = list {The new item did not make the list}
- Then Dispose (this)
- End; {Filter Down}
-
-
- FUNCTION Highest_Rated (var rating: integer_board): location_list;
- {Generates a list of the search_breadth highest rated locations, with}
- {the highest-rated ones at the bottom}
- Var
- x, y: byte;
- best_moves, this_move, next_move: location_list;
- Begin {Highest Rated}
- best_moves := nil;
- For x := 1 to search_breadth do
- Begin
- New (this_move);
- this_move^.value := 0;
- this_move^.next := best_moves;
- best_moves := this_move
- End;
- For x := 1 to board_size do
- For y := 1 to board_size do
- If rating [x, y] > 0
- Then Filter_Down (best_moves, x, y, rating [x, y]);
- this_move := best_moves; {Get rid of any leftover empty moves}
- While best_moves^.value = 0 do
- best_moves := best_moves^.next;
- While this_move <> best_moves do
- Begin
- next_move := this_move^.next;
- Dispose (this_move);
- this_move := next_move
- End;
- highest_rated := best_moves
- End; {Highest Rated}
-
-
- PROCEDURE Clean_Out_Location_List (var list: location_list);
- {Deallocates the list}
- Var
- next: location_list;
- Begin {Clean Out Location List}
- While list <> nil do
- Begin
- next := list^.next;
- Dispose (list);
- list := next
- End
- End; {Clean Out Location List}
-
-
- FUNCTION Count_Groups (var board: board_type): byte;
- {Debugging function--count the number of groups}
- Var
- tally: byte;
- Begin {Count Groups}
- tally := 0;
- While board.groups [tally + 1]^.size <> 0 do
- Inc (tally);
- count_groups := tally
- End; {Count Groups}
-
-
- END. {Arcana}
- SHAR_EOF
- fi
- if test -f 'brdhndlr.pas'
- then
- echo shar: "will not over-write existing file 'brdhndlr.pas'"
- else
- cat << \SHAR_EOF > 'brdhndlr.pas'
- UNIT BrdHndlr;
- {Board handling routines for Fumiko}
-
-
- INTERFACE
-
-
- USES
- CRT, GloblCTV, Arcana;
-
-
- FUNCTION Legal_Move (var x, y: byte; player: color_type; var board: board_type): boolean;
- {Returns true if the move in question is legal}
-
-
- FUNCTION Suicidal (var x, y: byte; var player: color_type; var board: board_type): boolean;
- {Returns true if the move in question would result in its own capture}
-
-
- PROCEDURE Draw_Board (var board: board_type);
- {Draws the board on the screen}
-
-
- PROCEDURE Get_User_Move (player: color_type; var board: board_type);
- {Gets a move from the user and updates the board}
-
-
- PROCEDURE Pass (var board: board_type);
- {Increments the pass and turn counters and clears the ko variable}
-
-
- PROCEDURE Play_At (var x, y: byte; var player: color_type; var board: board_type; depth: byte);
- {Plays at the indicated place and updates the groups, passes,}
- {turn, and ko variables}
-
-
- PROCEDURE Estimate_Score (var board: board_type; var b, w: integer; var points: integer_board);
- {Calculates influence for each point and adds up the score}
-
-
- PROCEDURE Show_Score (var board: board_type);
- {Estimates the score and shows the influence around the board}
-
-
- PROCEDURE Save_Board (var board: board_type; var filename: string);
- {Saves the board to a file and increments board_number}
-
-
- PROCEDURE Load_Board (var board: board_type; var filename: string);
- {Loads board from a file}
-
-
- PROCEDURE Delete_Board (var filename: string);
- {Deletes a saved board}
-
-
- PROCEDURE Show_Final_Score (var board: board_type);
- {Removes groups of aliveness 1-2, counts the score, and displays it}
-
-
- IMPLEMENTATION
-
-
- USES
- GroupMgr, Sequence;
-
-
- FUNCTION A_Neighboring_Point_Is_Unoccupied (var x, y: byte; var board: board_type): boolean;
- {Returns true if any adjacent point is empty. LOCAL}
- Var
- direction: direction_type;
- neighbor: location_type;
- found_one: boolean;
- Begin {A Neighboring Point Is Unoccupied}
- found_one := false;
- For direction := north to west do
- Begin
- neighbor := neighbors [x, y, direction];
- If on_board (neighbor.x, neighbor.y) and (board.data [neighbor.x, neighbor.y].color = empty)
- Then found_one := true
- End;
- a_neighboring_point_is_unoccupied := found_one
- End; {A Neighboring Point Is Unoccupied}
-
-
- FUNCTION Legal_Move (var x, y: byte; player: color_type; var board: board_type): boolean;
- {Returns true if the move in question is legal}
- Begin {Legal Move}
- If board.data [x, y].color <> empty
- Then legal_move := false
- Else If a_neighboring_point_is_unoccupied (x, y, board)
- Then legal_move := true
- Else legal_move := not ((board.ko.x = x) and (board.ko.y = y))
- End; {Legal Move}
-
-
- FUNCTION Suicidal (var x, y: byte; var player: color_type; var board: board_type): boolean;
- {Returns true if the move in question would result in its own capture}
- Var
- direction: direction_type;
- safe: boolean;
- adjacent_point: location_type;
- Begin {Suicidal}
- If a_neighboring_point_is_unoccupied (x, y, board)
- Then safe := true
- Else Begin
- safe := false;
- For direction := north to west do
- Begin
- adjacent_point := neighbors [x, y, direction];
- If on_board (adjacent_point.x, adjacent_point.y)
- Then If board.data [adjacent_point.x, adjacent_point.y].color = player
- Then Begin
- If board.groups [board.data [adjacent_point.x, adjacent_point.y].group]^.liberties > 1
- Then safe := true
- End
- Else If (board.data [adjacent_point.x, adjacent_point.y].color in [black_stone, white_stone])
- and (board.groups [board.data [adjacent_point.x, adjacent_point.y].group]^.liberties = 1)
- Then safe := true
- End
- End;
- suicidal := not safe
- End; {Suicidal}
-
-
- PROCEDURE Draw_Board (var board: board_type);
- {Draws the board on the screen}
- Var
- x, y: byte;
- c: char;
- Begin {Draw Board}
- ClrScr;
- WriteLn (board_index_line);
- For y := board_size downto 1 do
- Begin
- Write (y:2, ' ');
- For x := 1 to board_size do
- Begin
- c := screen_char (board.data [x, y].color);
- If (c = #250) and (x in [4, 10, 16]) and (y in [4, 10, 16])
- Then c := '+';
- Write (c, ' ')
- End;
- WriteLn (y)
- End;
- WriteLn (board_index_line);
- WriteLn
- End; {Draw Board}
-
-
- PROCEDURE Show_Influence (var group: group_type);
- {Displays the status of a group. LOCAL}
- Var
- x, y: byte;
- Begin {Show Influence}
- ClrScr;
- WriteLn (board_index_line);
- For y := board_size downto 1 do
- Begin
- Write (y:2, ' ');
- For x := 1 to board_size do
- If group.interpretation [x, y].too_distant
- Then Write ('. ')
- Else If group.interpretation [x, y].connected
- Then Write ('@ ')
- Else Write ('+ ');
- WriteLn (y)
- End;
- WriteLn (board_index_line);
- WriteLn;
- WriteLn ('Size: ', group.size, ' Liberties: ', group.liberties);
- WriteLn ('Aliveness: ', group.aliveness, ' Room: ', group.room);
- WriteLn ('To kill: ', group.to_kill.x, ',', group.to_kill.y, ' To save: ', group.to_save.x, ',', group.to_save.y);
- WriteLn ('(Return to continue)');
- ReadLn
- End; {Show Influence}
-
-
- PROCEDURE Show_Points_To_Attack (var group: group_type; var board: board_type);
- {Performs and displays results of find_points_to_attack. LOCAL}
- Var
- result: boolean_board;
- x, y: byte;
- points, here: location_list;
- Begin {Show Points To Attack}
- points := critical_points_for (group, board);
- ClrScr;
- WriteLn ('Points to attack that group: ');
- WriteLn;
- Draw_Board (board);
- here := points;
- While here <> nil do
- Begin
- GotoXY (2 + (2 * here^.location.x), 2 + board_size - here^.location.y);
- Write ('x');
- here := here^.next
- End;
- GotoXY (1, 4 + board_size);
- WriteLn ('(Return to continue)');
- Clean_Out_Location_List (points);
- ReadLn
- End; {Show Points To Attack}
-
-
- PROCEDURE Get_User_Move (player: color_type; var board: board_type);
- {Gets a move from the user and updates the board}
- Var
- move: string;
- x, y: byte;
- error_code: integer;
- legal: boolean;
- command: char;
- Begin {Get User Move}
- Repeat
- Draw_Board (board);
- Write ('Turn #', board.turn, ' ');
- If player = black_stone
- Then Write ('Black (*) to move: ')
- Else Write ('White (O) to move: ');
- ReadLn (move);
- legal := true;
- command := ' ';
- If move <> 'pass'
- Then Begin
- If move [1] in ['@', '!', '?', '<']
- Then Begin
- legal := false;
- command := move [1];
- move := copy (move, 2, 3)
- End;
- Case upcase (move [1]) of
- 'A'..'H': x := Ord (upcase (move [1])) - Ord ('A') + 1;
- 'J'..'T': x := Ord (upcase (move [1])) - Ord ('A')
- Else legal := false
- End;
- Val (copy (move, 2, 2), y, error_code);
- If legal
- Then legal := (error_code = 0) and (on_board (x, y));
- If legal
- Then legal := legal_move (x, y, player, board)
- End;
- If not legal
- Then If command <> ' '
- Then Begin
- If (on_board (x, y) and (board.data [x, y].group <> 0)) or (command in ['?', '<'])
- Then Case command of
- '@': Show_Influence (board.groups [board.data [x, y].group]^);
- '!': Show_Points_To_Attack (board.groups [board.data [x, y].group]^, board);
- '?': Begin
- WriteLn (strategy (board, player));
- WriteLn ('(Return to continue)');
- ReadLn
- End;
- '<': Begin
- Load_Board (board, last_board_filename);
- Draw_Board (board)
- End
- End
- End
- Else Begin
- Write ('That is not a legal move -- return to continue');
- ReadLn
- End
- Until legal;
- Save_Board (physical_board, last_board_filename);
- If move = 'pass'
- Then Pass (board)
- Else Play_At (x, y, player, board, 0)
- End; {Get User Move}
-
-
- PROCEDURE Pass (var board: board_type);
- {Increments the pass and turn counters and clears the ko variable}
- Begin {Pass}
- Inc (board.passes);
- Inc (board.turn);
- board.ko.x := 0;
- board.ko.y := 0
- End; {Pass}
-
-
- PROCEDURE Update_Relevant_Groups (var board: board_type; var changed: boolean_board; var depth: byte);
- {An ugly part of play_at. Updates all groups who do not consider all of}
- {the affected points too_distant. LOCAL}
- Var
- color: color_type;
- group, x, y: byte;
- Begin {Update Relevant Groups}
- group := 1;
- While board.groups [group]^.size <> 0 do
- Begin
- For x := 1 to board_size do
- For y := 1 to board_size do
- If changed [x, y] and (not board.groups [group]^.interpretation [x, y].too_distant)
- and (board.groups [group]^.last_update < board.turn)
- Then Update_Interpretation (group, board, depth);
- Inc (group)
- End
- End; {Update Relevant Groups}
-
-
- PROCEDURE Play_At (var x, y: byte; var player: color_type; var board: board_type; depth: byte);
- {Plays at the indicated place and updates the groups, passes,}
- {turns, and ko variables}
- Var
- direction: direction_type;
- neighbor: location_type;
- stones_captured: byte;
- changes: boolean_board;
- trickle: text;
- Begin {Play At}
- board.data [x, y].color := player;
- stones_captured := 0;
- Make_New_Group (x, y, board);
- changes := false_board;
- changes [x, y] := true;
- For direction := north to west do
- Begin
- neighbor := neighbors [x, y, direction];
- If on_board (neighbor.x, neighbor.y)
- Then Begin
- If board.data [neighbor.x, neighbor.y].color = player
- Then Begin
- If board.data [x, y].group <> board.data [neighbor.x, neighbor.y].group
- Then Merge_Groups (board.data [x, y].group, board.data [neighbor.x, neighbor.y].group, board)
- End
- Else If board.data [neighbor.x, neighbor.y].color in [black_stone, white_stone]
- Then Begin {It's occupied by the other player}
- Update_Interpretation (board.data [neighbor.x, neighbor.y].group, board, depth);
- If board.groups [board.data [neighbor.x, neighbor.y].group]^.liberties = 0
- Then Begin
- board.ko := neighbor;
- Inc (stones_captured,
- board.groups [board.data [neighbor.x, neighbor.y].group]^.size);
- Capture (board.data [neighbor.x, neighbor.y].group, board, changes, depth)
- End
- End
- End
- End;
- Update_Interpretation (board.data [x, y].group, board, depth);
- If (stones_captured = 0) and (board.groups [board.data [x, y].group]^.liberties = 0)
- Then Capture (board.data [x, y].group, board, changes, depth)
- Else If (stones_captured <> 1) or (board.groups [board.data [x, y].group]^.size <> 1)
- or (board.groups [board.data [x, y].group]^.liberties <> 1)
- Then Begin
- board.ko.x := 0;
- board.ko.y := 0
- End;
- Update_Relevant_Groups (board, changes, depth);
- board.passes := 0;
- If depth = 0
- Then Begin
- Assign (trickle, trickle_filename);
- Append (trickle);
- WriteLn (trickle, '+', board.turn);
- WriteLn (trickle, x);
- WriteLn (trickle, y);
- Close (trickle)
- End;
- Inc (board.turn)
- End; {Play At}
-
-
- PROCEDURE Estimate_Score (var board: board_type; var b, w: integer; var points: integer_board);
- {Calculates influence for each point and adds up the score}
- Var
- x, y, group: byte;
- influence: integer;
- color: color_type;
- Begin {Estimate Score}
- b := 0;
- w := 0;
- For x := 1 to board_size do
- For y := 1 to board_size do
- Begin
- points [x, y] := 0;
- group := 1;
- While board.groups [group]^.size <> 0 do
- Begin
- If board.groups [group]^.interpretation [x, y].connected
- Then Begin
- If board.groups [group]^.interpretation [x, y].member
- Then influence := board.groups [group]^.aliveness * 200
- Else influence := (board.groups [group]^.aliveness * 100)
- div board.groups [group]^.interpretation [x, y].distance;
- If board.groups [group]^.owner = black_stone
- Then points [x, y] := points [x, y] + influence
- Else points [x, y] := points [x, y] - influence
- End;
- Inc (group)
- End;
- If points [x, y] > 0
- Then b := b + 1
- Else If points [x, y] < 0
- Then w := w + 1
- End
- End; {Estimate Score}
-
-
- PROCEDURE Show_Score (var board: board_type);
- {Estimates the score and shows the influence around the board}
- Var
- x, y: byte;
- b, w: integer;
- points: integer_board;
- Begin {Show Score}
- Estimate_Score (board, b, w, points);
- ClrScr;
- WriteLn (board_index_line);
- For y := board_size downto 1 do
- Begin
- Write (y:2, ' ');
- For x := 1 to board_size do
- Case points [x, y] of
- -maxint..-1000: Write ('W ');
- -999..-100: Write ('w ');
- -99..-1: Write ('- ');
- 0: Write ('. ');
- 1..99: Write ('| ');
- 100..999: Write ('b ');
- 1000..maxint: Write ('B ')
- End;
- WriteLn (y)
- End;
- WriteLn (board_index_line);
- WriteLn;
- WriteLn ('Black: ', b, ' White: ', w);
- ReadLn
- End; {Show Score}
-
-
- PROCEDURE Save_Board (var board: board_type; var filename: string);
- {Saves the board to a file and increments board_number}
- Var
- board_file: board_file_type;
- group_file: group_file_type;
- count: byte;
- Begin {Save Board}
- If Copy (filename, 3, 8) <> 'PHYSICAL'
- Then Begin
- Str (board_number, filename);
- filename := drive_name + 'F' + filename;
- board_number := (board_number + 1) mod maxint
- End;
- Assign (board_file, filename + '.BRD');
- Rewrite (board_file);
- Write (board_file, board);
- Close (board_file);
- Assign (group_file, filename + '.GRP');
- Rewrite (group_file);
- count := 1;
- While (count <= max_groups) and (board.groups [count]^.size <> 0) do
- Begin
- Write (group_file, board.groups [count]^);
- Inc (count)
- End;
- Close (group_file)
- End; {Save Board}
-
-
- PROCEDURE Load_Board (var board: board_type; var filename: string);
- {Loads board from a file}
- Var
- board_file: board_file_type;
- group_file: group_file_type;
- count: byte;
- Begin {Load Board}
- Assign (board_file, filename + '.BRD');
- {$I-}
- Reset (board_file);
- {$I+}
- If ioresult = 0
- Then Begin
- Read (board_file, board);
- Close (board_file);
- Assign (group_file, filename + '.GRP');
- Reset (group_file);
- count := 1;
- While not eof (group_file) do
- Begin
- Read (group_file, board.groups [count]^);
- Inc (count)
- End;
- Close (group_file);
- If (count <= max_groups)
- Then board.groups [count]^.size := 0
- End
- End; {Load Board}
-
-
- PROCEDURE Delete_Board (var filename: string);
- {Deletes a saved board}
- Var
- deadfile: file;
- Begin {Delete Board}
- Assign (deadfile, filename + '.BRD');
- Erase (deadfile);
- Assign (deadfile, filename + '.GRP');
- Erase (deadfile)
- End; {Delete Board}
-
-
- PROCEDURE Show_Final_Score (var board: board_type);
- {Removes groups of aliveness 1-2, counts the score, and displays it}
- Var
- count, changes, x, y, black_neighbors, white_neighbors: byte;
- counted: boolean_board;
- black_score, white_score: real;
- score_array: integer_board;
- done: boolean;
- direction: direction_type;
- neighbor: location_type;
- Begin {Show Final Score}
- count := 1;
- time_left := maxint;
- WriteLn ('Counting score...');
- Repeat
- changes := 0;
- While board.groups [count]^.size <> 0 do
- Begin
- Update_Interpretation (count, board, 0);
- If board.groups [count]^.aliveness < 3
- Then Begin
- Capture (count, board, counted, 0);
- Inc (changes)
- End
- Else Inc (count);
- WriteLn ('Next: size ', board.groups [count]^.size)
- End
- Until changes = 0;
- Draw_Board (board);
- black_score := 0;
- white_score := 0;
- score_array := zero_board;
- counted := false_board;
- Repeat
- For x := 1 to board_size do
- For y := 1 to board_size do
- If not counted [x, y]
- Then Begin
- count := 0;
- If board.data [x, y].color in [black_stone, white_stone]
- Then If board.data [x, y].color = black_stone
- Then score_array [x, y] := 12
- Else score_array [x, y] := -12
- Else Begin
- white_neighbors := 0;
- black_neighbors := 0;
- For direction := north to west do
- Begin
- neighbor := neighbors [x, y, direction];
- If on_board (neighbor.x, neighbor.y)
- Then If board.data [neighbor.x, neighbor.y].color = black_stone
- Then Inc (black_neighbors)
- Else If board.data [neighbor.x, neighbor.y].color = white_stone
- Then Inc (white_neighbors)
- End;
- Case (black_neighbors + white_neighbors) of
- 1: score_array [x, y] := (12 * black_neighbors) - (12 * white_neighbors);
- 2: score_array [x, y] := (6 * black_neighbors) - (6 * white_neighbors);
- 3: score_array [x, y] := (4 * black_neighbors) - (4 * white_neighbors);
- 4: score_array [x, y] := (3 * black_neighbors) - (3 * white_neighbors)
- Else Begin
- For direction := north to west do
- Begin
- neighbor := neighbors [x, y, direction];
- If on_board (neighbor.x, neighbor.y)
- Then Begin
- If counted [neighbor.x, neighbor.y]
- Then Inc (count);
- If score_array [neighbor.x, neighbor.y] > 0
- Then Inc (black_neighbors)
- Else If score_array [neighbor.x, neighbor.y] < 0
- Then Inc (white_neighbors)
- End
- End;
- If (black_neighbors > 0) and (white_neighbors > 0)
- Then score_array [x, y] := 0
- Else Case (black_neighbors + white_neighbors) of
- 1: score_array [x, y] := (12 * black_neighbors) - (12 * white_neighbors);
- 2: score_array [x, y] := (6 * black_neighbors) - (6 * white_neighbors);
- 3: score_array [x, y] := (4 * black_neighbors) - (4 * white_neighbors);
- 4: score_array [x, y] := (3 * black_neighbors) - (3 * white_neighbors)
- End
- End
- End
- End;
- counted [x, y] := (black_neighbors > 0) or (white_neighbors > 0);
- If counted [x, y]
- Then If score_array [x, y] > 0
- Then black_score := black_score + (score_array [x, y] / 12)
- Else white_score := white_score - (score_array [x, y] / 12)
- Else score_array [x, y] := 0
- End;
- done := true;
- For x := 1 to board_size do
- For y := 1 to board_size do
- If not counted [x, y]
- Then done := false
- Until done;
- WriteLn;
- WriteLn ('Black: ', black_score:6:2, ' White: ', white_score:6:2)
- End; {Show Final Score}
-
-
- END. {BrdHndlr}
- SHAR_EOF
- fi
- if test -f 'butler.pas'
- then
- echo shar: "will not over-write existing file 'butler.pas'"
- else
- cat << \SHAR_EOF > 'butler.pas'
- UNIT Butler;
- {Setup and cleanup procedures for Fumiko}
-
-
- INTERFACE
-
-
- USES
- Dos, Crt, GloblCTV, Arcana;
-
-
- PROCEDURE Initialize;
- {Initializes all of the variables at the beginning of a run}
-
-
- IMPLEMENTATION
-
-
- USES
- BrdHndlr;
-
-
- PROCEDURE Find_Neighbor_Coordinates (var x, y: byte; var direction: direction_type; var neighbor: location_type);
- {Finds the coordinates of a point's neighbor in the specified direction,}
- {and returns (0, 0) if the result is off the board. LOCAL}
- Begin {Find Neighbor Coordinates}
- neighbor.x := x;
- neighbor.y := y;
- Case direction of
- north: neighbor.y := y + 1;
- south: neighbor.y := y - 1;
- east: neighbor.x := x + 1;
- west: neighbor.x := x - 1;
- nw: Begin
- neighbor.x := x - 1;
- neighbor.y := y + 1
- End;
- sw: Begin
- neighbor.x := x - 1;
- neighbor.y := y - 1
- End;
- se: Begin
- neighbor.x := x + 1;
- neighbor.y := y - 1
- End;
- ne: Begin
- neighbor.x := x + 1;
- neighbor.y := y + 1
- End
- End;
- If not on_board (neighbor.x, neighbor.y)
- Then Begin
- neighbor.x := 0;
- neighbor.y := 0
- End
- End; {Find Neighbor Coordinates}
-
-
- PROCEDURE Build_Neighbor_Array;
- {Builds the neighboring-coordinates array. LOCAL}
- Var
- x, y: byte;
- direction: direction_type;
- Begin {Build Neighbor Array}
- For x := 1 to board_size do
- For y := 1 to board_size do
- For direction := north to ne do
- Begin
- Find_Neighbor_Coordinates (x, y, direction, neighbors [x, y, direction]);
- If not on_board (neighbors [x, y, direction].x, neighbors [x, y, direction].y)
- Then Begin
- neighbors [x, y, direction].x := 0;
- neighbors [x, y, direction].y := 0
- End
- End;
- For direction := north to ne do
- Begin
- neighbors [0, 0, direction].x := 0;
- neighbors [0, 0, direction].y := 0
- End
- End; {Build Neighbor Array}
-
-
- PROCEDURE Clear_Off (var board: board_type);
- {Sets all board points to empty, group pointers to nil. LOCAL}
- Var
- x, y: byte;
- Begin {Clear Off}
- With board do
- Begin
- For x := 1 to board_size do
- For y := 1 to board_size do
- With data [x, y] do
- Begin
- color := empty;
- group := 0;
- End;
- For x := 1 to max_groups do
- Begin
- New (groups [x]);
- groups [x]^.size := 0;
- groups [x]^.to_kill.x := 0;
- groups [x]^.to_kill.y := 0;
- groups [x]^.to_save.x := 0;
- groups [x]^.to_save.y := 0
- End;
- ko.x := 0;
- ko.y := 0;
- passes := 0;
- turn := 1
- End
- End; {Clear Off}
-
-
- PROCEDURE Set_Terrain (var terrain: integer_board);
- Var
- x, y: byte;
- Begin {Set Terrain}
- terrain := zero_board;
- For x := 1 to board_size do
- For y := 1 to board_size do
- Begin
- Case line (x) of
- 2, 8..board_size: Inc (terrain [x, y], 1000);
- 3, 4: Inc (terrain [x, y], 5000);
- 5..7: Inc (terrain [x, y], 5000 - (1000 * (line (x) - 4)))
- End;
- Case line (y) of
- 2, 8..board_size: Inc (terrain [x, y], 1000);
- 3, 4: Inc (terrain [x, y], 5000);
- 5..7: Inc (terrain [x, y], 5000 - (1000 * (line (y) - 4)))
- End;
- End
- End; {Set Terrain}
-
-
- PROCEDURE Load_Trickle_File (var board: board_type);
- {Tournament kludge--loads and plays saved moves}
- Var
- trickle: text;
- x, y: byte;
- move_number: integer;
- player: color_type;
- Begin {Load Trickle File}
- Assign (trickle, trickle_filename);
- Reset (trickle);
- While not eof (trickle) do
- Begin
- ReadLn (trickle, move_number);
- ReadLn (trickle, x);
- ReadLn (trickle, y);
- If odd (move_number)
- Then player := black_stone
- Else player := white_stone;
- Play_At (x, y, player, board, maximum_search_depth)
- End;
- Close (trickle)
- End; {Load Trickle File}
-
-
- PROCEDURE Initialize;
- {Initializes all of the variables at the beginning of a run}
- Var
- x, y: byte;
- answer: string;
- trickle: text;
- Begin {Initialize}
- ClrScr;
- WriteLn ('FUMIKO -- An artificially intelligent Go program');
- WriteLn;
- WriteLn;
- WriteLn ('"A path is formed by walking on it."');
- WriteLn (' -Chuang Tsu');
- WriteLn;
- WriteLn;
- Write ('How many minutes will each player get? ');
- ReadLn (total_time);
- total_time := total_time * 60;
- time_left := total_time;
- Build_Neighbor_Array;
- Clear_Off (physical_board);
- board_index_line := ' ';
- For x := 1 to board_size do
- If x < 9
- Then board_index_line := board_index_line + Chr (x + Ord ('A') - 1) + ' '
- Else board_index_line := board_index_line + Chr (x + Ord ('A')) + ' ';
- For x := 1 to board_size do
- For y := 1 to board_size do
- Begin
- false_board [x, y] := false;
- zero_board [x, y] := 0
- End;
- Set_Terrain (terrain);
- Write ('Enter name of board drive (for example, "e:"): ');
- ReadLn (drive_name);
- last_board_filename := drive_name + 'PHYSICAL';
- trickle_filename := drive_name + 'TRICKLE.FUM';
- board_number := 1;
- WriteLN ('New game, saved board, or trickle (n/s/t)? ');
- ReadLn (answer);
- Case upcase (answer [1]) of
- 'N': Begin
- Assign (trickle, trickle_filename);
- Rewrite (trickle);
- Close (trickle)
- End;
- 'S': Load_Board (physical_board, last_board_filename);
- 'T': Load_Trickle_File (physical_board);
- End;
- Write ('What color should I play (b/w)? ');
- ReadLn (answer);
- If upcase (answer [1]) = 'W'
- Then my_color := white_stone
- Else my_color := black_stone;
- If not odd (physical_board.turn)
- Then next_player := white_stone
- Else next_player := black_stone;
- Draw_Board (physical_board)
- End; {Initialize}
-
-
- END. {Butler}
-
- SHAR_EOF
- fi
- if test -f 'fumiko.doc'
- then
- echo shar: "will not over-write existing file 'fumiko.doc'"
- else
- cat << \SHAR_EOF > 'fumiko.doc'
- This is my first fully-functional go program, entitled Fumiko. I am
- abandoning it and beginning a new one, to be entitled "HellGo". I wish
- to make the .exe and .pas code available for everyone to browse, play with,
- and learn from.
-
- I don't know much about FTP-ing, so could you please copy the following files
- up to the directory there? They are in /home/jupiter/student/pdudey, and
- should be readable.
-
- SOURCE CODE
- globlctv.pas
- arcana.pas
- butler.pas
- groupmgr.pas
- brdhndlr.pas
- tactics.pas
- sequence.pas
- fumiko.pas (The main program)
-
- EXECUTABLE CODE (IBM compatible)
- fumiko.exe
-
- NECESSARY SPARE FILE
- trickle.fum
-
- That last one isn't actually -necessary-, but the program needs to have an
- extant file of that name.
-
- A WORD OF WARNING: When doing lookahead, Fumiko writes to disk quite often.
- If you can install a ramdrive of 1 meg or bigger, do it!
-
- The program is written in Turbo Pascal 6.0.
-
- ------------------------------------------------------------------------------
- When you run the program, she (black = yin = female) will ask how much time
- each player gets. She's supposed to panic when she gets low on time; that
- feature doesn't work perfectly, but I'm starting on a new program and don't
- want to mess with it; if you care, you've got the source code! (Look in
- arcana and sequence, I think).
-
- A saved game must consist of two files: physical.brd and physical.grp.
- Fumiko saves the game every turn.
-
- Loading from the trickle file replays all of the moves that have been played.
- You can edit this file (to correct typos &c)--each move is a turn number
- (preceded by a '+'), and row, and a column.
-
- At the 'to move' prompt, type coordinates--e.g., 'b13', or 'pass'.
-
- You can also type '<' to back up. THIS WILL MESS UP THE TRICKLE FILE.
-
- Typing @b6 will give you the status of the group at b6. Typing !b6 will
- suggest points to attack it. Typing ? will suggest a strategy.
-
- I think that does it. Email me at pdudey@willamette.edu if you have any
- questions, comments, or suggestions. I plan to make HellGo public when it
- is done, too; I'm working on a grant, so I don't have to keep my stuff
- secret!
-
- SHAR_EOF
- fi
- if test -f 'fumiko.pas'
- then
- echo shar: "will not over-write existing file 'fumiko.pas'"
- else
- cat << \SHAR_EOF > 'fumiko.pas'
- PROGRAM Fumiko;
- {Artificially intelligent, learning Go program}
-
-
- USES
- GloblCTV, Arcana, Butler, GroupMgr, BrdHndlr, Sequence;
-
-
- BEGIN {Fumiko}
- Initialize;
- Repeat
- If next_player = my_color
- Then Play_Move (physical_board, my_color, 0)
- Else Begin
- Get_User_Move (next_player, physical_board);
- Draw_Board (physical_board)
- End;
- next_player := opposite (next_player)
- Until physical_board.passes >= 2;
- WriteLn;
- Write ('(Return to remove dead groups)');
- ReadLn;
- Show_Final_Score (physical_board);
- WriteLn;
- Delete_Board (last_board_filename);
- Write ('(Return to go back to DOS)');
- ReadLn
- END. {Fumiko}
-
- SHAR_EOF
- fi
- if test -f 'globlctv.pas'
- then
- echo shar: "will not over-write existing file 'globlctv.pas'"
- else
- cat << \SHAR_EOF > 'globlctv.pas'
- UNIT GloblCTV;
- {Global constants, types, and variables for Fumiko}
-
-
- INTERFACE
-
-
- CONST
-
- board_size = 19;
-
- board_size_squared = board_size * board_size;
-
- maximum_search_depth = 4;
-
- search_breadth = 3;
-
- max_groups = 180;
-
-
- TYPE
-
- direction_type = (north, south, east, west, nw, sw, se, ne);
-
- color_type = (black_stone, white_stone, empty, off_board); {'black' and 'white' are used by the CRT unit}
-
- location_type = record
- x, y: byte
- end;
-
- location_list = ^location_list_node;
-
- location_list_node = record
- value: integer;
- location: location_type;
- next: location_list
- end;
-
- boolean_board = array [1..board_size, 1..board_size] of boolean;
-
- integer_board = array [1..board_size, 1..board_size] of integer;
-
- point_interpretation = record
- too_distant, member, adjacent, connected: boolean;
- distance: byte
- end;
-
- board_interpretation = array [1..board_size, 1..board_size] of point_interpretation;
-
- group_pointer = ^group_type;
-
- group_type = record
- size, liberties, aliveness, room: byte;
- last_update: integer;
- owner: color_type;
- interpretation: board_interpretation;
- to_kill, to_save: location_type
- end;
-
- board_space = record
- color: color_type;
- group: byte
- end;
-
- board_type = record
- data: array [1..board_size, 1..board_size] of board_space; {0, 0 is an off_board space}
- ko: location_type;
- passes: byte;
- turn: integer;
- groups: array [1..max_groups] of group_pointer
- end;
-
- board_file_type = file of board_type;
-
- group_file_type = file of group_type;
-
-
- VAR
- physical_board: board_type;
- neighbors: array [0..board_size, 0..board_size, direction_type] of location_type;
- false_board: boolean_board;
- board_index_line: string;
- board_number: longint;
- my_color: color_type;
- zero_board, terrain: integer_board;
- total_time, time_left: word;
- last_board_filename, drive_name, trickle_filename: string;
- next_player: color_type;
-
-
- IMPLEMENTATION
-
- END. {GloblCTV}
-
- SHAR_EOF
- fi
- if test -f 'groupmgr.pas'
- then
- echo shar: "will not over-write existing file 'groupmgr.pas'"
- else
- cat << \SHAR_EOF > 'groupmgr.pas'
- UNIT GroupMgr;
- {Group manager for Fumiko}
-
-
- INTERFACE
-
-
- USES
- CRT, GloblCTV, Arcana, Butler;
-
-
- PROCEDURE Make_New_Group (var x, y: byte; var board: board_type);
- {Builds a new group containing one stone, in terms of size and members}
- {only, and adds it to the group list}
-
-
- PROCEDURE Merge_Groups (a, b: byte; var board: board_type);
- {Merges two groups, in terms of size and members only,}
- {and removes group b from the group list}
-
-
- FUNCTION Maneuvering_Room (var x, y: byte; var owner: color_type; var board: board_type): boolean;
- {Returns true if the point is not controlled by the other player}
-
-
- PROCEDURE Update_Interpretation (var group: byte; var board: board_type; depth: byte);
- {Updates a group's interpretation of its surroundings}
-
-
- PROCEDURE Capture (group: byte; var board: board_type; var changes: boolean_board; depth: byte);
- {Removes the offending stones from the board and their group from the list}
- {Also updates the liberties of nearby groups}
-
-
- IMPLEMENTATION
-
-
- USES
- Sequence;
-
-
- PROCEDURE Make_New_Group (var x, y: byte; var board: board_type);
- {Builds a new group containing one stone, in terms of size and members}
- {only, and adds it to the group list}
- Var
- a, b: byte;
- Begin {Make New Group}
- board.data [x, y].group := 1;
- While board.groups [board.data [x, y].group]^.size <> 0 do
- Inc (board.data [x, y].group);
- With board.groups [board.data [x, y].group]^ do
- Begin
- size := 1;
- aliveness := 4;
- room := 0;
- last_update := 0;
- owner := board.data [x, y].color;
- to_kill.x := 0;
- to_kill.y := 0;
- For a := 1 to board_size do {List members in minimal interpretation}
- For b := 1 to board_size do
- interpretation [a, b].member := false;
- interpretation [x, y].member := true
- End;
- board.groups [board.data [x, y].group + 1]^.size := 0
- End; {Make New Group}
-
-
- PROCEDURE Merge_Groups (a, b: byte; var board: board_type);
- {Merges two groups, in terms of size and members only,}
- {and removes group b from the group list}
- Var
- x, y: byte;
- Begin {Merge Groups}
- board.groups [a]^.size := board.groups [a]^.size + board.groups [b]^.size;
- If board.groups [b]^.aliveness > board.groups [a]^.aliveness {The new group has the stronger of the two lives}
- Then board.groups [a]^.aliveness := board.groups [b]^.aliveness;
- If board.groups [b]^.last_update < board.groups [a]^.last_update {The new group has the least recent update}
- Then board.groups [a]^.last_update := board.groups [b]^.last_update;
- For x := 1 to board_size do
- For y := 1 to board_size do
- If board.groups [a]^.interpretation [x, y].member or board.groups [b]^.interpretation [x, y].member
- Then Begin
- board.groups [a]^.interpretation [x, y].member := true;
- board.data [x, y].group := a
- End;
- Compress (b, board)
- End; {Merge Groups}
-
-
- FUNCTION Maneuvering_Room (var x, y: byte; var owner: color_type; var board: board_type): boolean;
- {Returns true if the point is not controlled by the other player}
- Var
- direction: direction_type;
- theirs: byte;
- neighbor: location_type;
- Begin {Maneuvering Room}
- If (board.data [x, y].color = empty) or
- ((board.data [x, y].color = opposite (owner)) and (board.groups [board.data [x, y].group]^.aliveness < 3))
- Then Begin
- theirs := 0;
- For direction := north to ne do
- Begin
- neighbor := neighbors [x, y, direction];
- If on_board (neighbor.x, neighbor.y)
- Then With board.data [neighbors [x, y, direction].x, neighbors [x, y, direction].y] do
- If (color = opposite (owner)) and (board.groups [group]^.aliveness > 2)
- Then If direction in [north..west]
- Then Inc (theirs, 2)
- Else Inc (theirs, 1)
- End;
- maneuvering_room := (theirs = 0) or ((theirs = 1) and (not on_edge (x)) and (not on_edge (y)))
- End
- Else maneuvering_room := false
- End; {Maneuvering Room}
-
-
- PROCEDURE Consider_Point (var x, y: byte; var group: group_type; var interpretation: point_interpretation;
- var board: board_type; var distance: byte);
- {The ugly part of look_around. Updates the interpretation of a}
- {particular point. LOCAL}
- Begin {Consider Point}
- If interpretation.too_distant
- Then Begin {The group has influence here--analyze it}
- interpretation.too_distant := false;
- interpretation.distance := distance;
- If board.data [x, y].color = empty
- Then Begin
- If maneuvering_room (x, y, group.owner, board)
- Then Begin
- interpretation.connected := true;
- Inc (group.room);
- End
- End
- Else If board.data [x, y].color = group.owner
- Then Begin
- interpretation.connected := true;
- If board.groups [board.data [x, y].group]^.aliveness > group.aliveness
- Then group.aliveness := board.groups [board.data [x, y].group]^.aliveness
- Else board.groups [board.data [x, y].group]^.aliveness := group.aliveness
- End
- Else If maneuvering_room (x, y, group.owner, board)
- Then Begin
- interpretation.connected := true;
- Inc (group.room)
- End
- End
- End; {Consider Point}
-
-
- PROCEDURE Look_Around (var group: group_type; distance, previous_differences: byte; var board: board_type);
- {The recursive part of update_interpretation. Looks at points}
- {around those not labelled too_distant. LOCAL}
- Var
- x, y, differences: byte;
- new_interpretation: ^board_interpretation;
- direction: direction_type;
- point: location_type;
- Begin {Look Around}
- New (new_interpretation);
- new_interpretation^ := group.interpretation;
- For x := 1 to board_size do
- For y := 1 to board_size do
- If (group.interpretation [x, y].connected) and not
- ((board.data [x, y].group <> 0) and (board.groups [board.data [x, y].group]^.aliveness > 3)
- and (board.data [x, y].color = opposite (group.owner)))
- Then For direction := north to west do
- Begin
- point := neighbors [x, y, direction];
- If on_board (point.x, point.y)
- Then Consider_Point (point.x, point.y, group, new_interpretation^ [point.x, point.y], board, distance);
- End;
- differences := 0;
- For x := 1 to board_size do
- For y := 1 to board_size do
- Begin
- If not equal (group.interpretation [x, y], new_interpretation^ [x, y], point_interpretation_size)
- Then Inc (differences);
- group.interpretation [x, y] := new_interpretation^ [x, y]
- End;
- Dispose (new_interpretation);
- If (distance < group.aliveness) and (differences > previous_differences)
- Then Look_Around (group, distance + 1, differences, board)
- End; {Look Around}
-
-
- PROCEDURE Clear_Out (var interpretation: board_interpretation);
- {Sets all interpretation bits to false, except member (which is}
- {unaffected) and too_distant (which is := not member). LOCAL}
- Var
- x, y: byte;
- Begin {Clear Out}
- For x := 1 to board_size do
- For y := 1 to board_size do
- With interpretation [x, y] do
- Begin
- too_distant := not member;
- adjacent := false;
- connected := false;
- distance := 255
- End
- End; {Clear Out}
-
-
- PROCEDURE Consider_Adjacent_Point (var x, y: byte; var group: group_type; var board: board_type);
- {The ugly part of update_interpretation. Updates the interpretation of}
- {a particular adjacent point. LOCAL}
- Begin {Consider Adjacent Point}
- If (on_board (x, y)) and (group.interpretation [x, y].too_distant)
- Then With group.interpretation [x, y] do
- Begin
- too_distant := false;
- adjacent := true;
- group.interpretation [x, y].distance := 1;
- If board.data [x, y].color = empty
- Then Begin
- If maneuvering_room (x, y, group.owner, board)
- Then Begin
- Inc (group.room);
- connected := true
- End;
- Inc (group.liberties)
- End
- Else If board.data [x, y].color in [black_stone, white_stone]
- {It must be occupied by the opponent}
- Then Begin
- If maneuvering_room (x, y, group.owner, board)
- Then Begin
- connected := true;
- Inc (group.room)
- End
- End
- End
- End; {Consider Adjacent Point}
-
-
- PROCEDURE Update_Interpretation (var group: byte; var board: board_type; depth: byte);
- {Updates a group's interpretation of its surroundings}
- Var
- x, y: byte;
- direction: direction_type;
- neighbor: location_type;
- Begin {Update Interpretation}
- With board.groups [group]^ do
- Begin
- liberties := 0;
- room := 0;
- last_update := board.turn;
- Clear_Out (interpretation);
- For x := 1 to board_size do
- For y := 1 to board_size do
- If interpretation [x, y].member
- Then Begin
- interpretation [x, y].connected := true;
- interpretation [x, y].distance := 0;
- For direction := north to west do
- Begin
- neighbor := neighbors [x, y, direction];
- Consider_Adjacent_Point (neighbor.x, neighbor.y, board.groups [group]^, board);
- End
- End;
- If depth <= maximum_search_depth
- Then Begin
- Look_Around (board.groups [group]^, 2, 0, board);
- Update_Aliveness (board.groups [group]^, board, depth)
- End
- End
- End; {Update Interpretation}
-
-
- PROCEDURE Capture (group: byte; var board: board_type; var changes: boolean_board; depth: byte);
- {Removes the offending stones from the board and their group from the list}
- {Also updates the liberties of nearby groups}
- Var
- x, y: byte;
- Begin {Capture}
- For x := 1 to board_size do
- For y := 1 to board_size do
- If board.groups [group]^.interpretation [x, y].member
- Then Begin
- changes [x, y] := true;
- board.data [x, y].color := empty;
- board.data [x, y].group := 0
- End;
- Compress (group, board)
- End; {Capture}
-
-
- END. {GroupMgr}
-
- SHAR_EOF
- fi
- if test -f 'sequence.pas'
- then
- echo shar: "will not over-write existing file 'sequence.pas'"
- else
- cat << \SHAR_EOF > 'sequence.pas'
- UNIT Sequence;
- {Life and death and sequencing routines for Fumiko}
-
-
- INTERFACE
-
-
- USES
- Dos, CRT, GloblCTV, Arcana, Butler;
-
-
- FUNCTION Alive (var group: group_type; var board: board_type; depth: byte): boolean;
- {Returns true if the group in question is unconditionally alive}
-
-
- FUNCTION Life (group: group_type; first_player: color_type; var board: board_type;
- depth: byte; var crucial_move: location_type): byte;
- {The big one, folks! Reads out sequences to see if the group in question}
- {can live.}
-
-
- PROCEDURE Update_Aliveness (var group: group_type; var board: board_type; var depth: byte);
- {Rates the group's life from 1 (hosed) to 7 (2 eyes)}
-
-
- FUNCTION Critical_Points_For (var group: group_type; var board: board_type): location_list;
- {Generates a list of moves to consider in attacking/defending the group}
-
-
- FUNCTION Strategy (var board: board_type; var player: color_type): string;
- {Returns a string suggesting territory, defense, or offense}
-
-
- PROCEDURE Pick_Move (var board: board_type; player: color_type; var move: location_list; depth: byte);
- {Picks a strategy and calls the appropriate tacticians}
-
-
- PROCEDURE Play_Move (var board: board_type; player: color_type; depth: byte);
- {Calls pick_move and plays it}
-
-
- IMPLEMENTATION
-
-
- USES
- GroupMgr, BrdHndlr, Tactics;
-
-
- PROCEDURE Fill_Liberties (var x, y: byte; var liberty: boolean_board; var board: board_type;
- var new_move_made: boolean; var depth: byte);
- {An ugly part of alive. Plays opposing stones in liberties of the group}
- {in question. LOCAL}
- Var
- a, b: byte;
- attacker: color_type;
- Begin {Fill Liberties}
- new_move_made := false;
- attacker := opposite (board.groups [board.data [x, y].group]^.owner);
- For a := 1 to board_size do
- For b := 1 to board_size do
- If board.data [x, y].group <> 0
- Then Begin
- If liberty [a, b]
- Then If legal_move (a, b, attacker, board)
- and not suicidal (a, b, attacker, board)
- Then Begin
- liberty [a, b] := false;
- new_move_made := true;
- Play_At (a, b, attacker, board, 1 + maximum_search_depth)
- End
- End
- End; {Fill Liberties}
-
-
- PROCEDURE Add_Helpful_Groups (var group: byte; var liberty: boolean_board; var board: board_type; var addition: boolean);
- {Another ugly part of alive. Finds groups that share liberties with the}
- {group in question, and add their liberties to the liberty array. LOCAL}
- Var
- x, y, a, b: byte;
- direction: direction_type;
- neighbor: location_type;
- Begin {Add Helpful Groups}
- addition := false;
- For x := 1 to board_size do
- For y := 1 to board_size do
- If liberty [x, y]
- Then Begin {See if any other groups border on this liberty}
- For direction := north to west do
- Begin
- neighbor := neighbors [x, y, direction];
- If (on_board (neighbor.x, neighbor.y))
- and (board.data [neighbor.x, neighbor.y].color = board.groups [group]^.owner)
- and not (board.data [neighbor.x, neighbor.y].group = group)
- Then Begin
- For a := 1 to board_size do
- For b := 1 to board_size do
- If board.groups [board.data [neighbor.x, neighbor.y].group]^
- .interpretation [a, b].adjacent and
- (board.data [neighbor.x, neighbor.y].color = empty)
- Then If not liberty [a, b]
- Then Begin
- liberty [a, b] := true;
- addition := true
- End
- End
- End
- End
- End; {Add Helpful Groups}
-
-
- FUNCTION Alive (var group: group_type; var board: board_type; depth: byte): boolean;
- {Returns true if the group in question is unconditionally alive}
- Var
- x, y: byte;
- member: location_type;
- liberty: boolean_board;
- new_move_made, another_group_added: boolean;
- previous_board_file: string;
- Begin {Alive}
- Write (depth, ' ');
- Save_Board (board, previous_board_file);
- For x := 1 to board_size do
- For y := 1 to board_size do
- liberty [x, y] := group.interpretation [x, y].adjacent
- and (board.data [x, y].color = empty);
- Find_Member (group, member);
- Repeat
- Repeat
- Fill_Liberties (member.x, member.y, liberty, board, new_move_made, depth)
- Until (not new_move_made)
- or (board.data [member.x, member.y].group = 0)
- or (board.groups [board.data [member.x, member.y].group]^.liberties = 1);
- If (not new_move_made)
- and (board.data [member.x, member.y].group <> 0)
- and (board.groups [board.data [member.x, member.y].group]^.liberties > 1)
- Then Add_Helpful_Groups (board.data [member.x, member.y].group, liberty, board, another_group_added)
- Else another_group_added := false
- Until not another_group_added;
- alive := (board.data [member.x, member.y].group <> 0)
- and (board.groups [board.data [member.x, member.y].group]^.liberties > 1);
- Load_Board (board, previous_board_file);
- Delete_Board (previous_board_file)
- End; {Alive}
-
-
- FUNCTION Aji (var group: group_type; var board: board_type): byte;
- {Performs a static analysis on a group's life. LOCAL}
- Var
- x, y: byte;
- neighbor_in_atari: boolean;
- Begin {Aji}
- If group.liberties = 1
- Then Begin
- neighbor_in_atari := false;
- For x := 1 to board_size do
- For y := 1 to board_size do
- With group.interpretation [x, y] do
- If adjacent and (board.data [x, y].color = opposite (group.owner))
- and (board.groups [board.data [x, y].group]^.liberties = 1)
- Then neighbor_in_atari := true;
- If neighbor_in_atari
- Then aji := 4 {Unknown}
- Else aji := 1 {Hosed}
- End
- Else Case group.room of
- 0..1: aji := 2; {Probably hosed}
- 10..255: aji := 6 {Probably alive}
- Else aji := 4 {Unknown}
- End
- End; {Aji}
-
-
- FUNCTION Life (group: group_type; first_player: color_type; var board: board_type;
- depth: byte; var crucial_move: location_type): byte;
- {The big one, folks! Reads out sequences to see if the group in question}
- {can live.}
- Var
- points_to_consider, move: location_list;
- where: location_type;
- best: byte;
- defending, found_one: boolean;
- previous_board_file: string;
- Begin {Life}
- If (depth >= maximum_search_depth) or (group.aliveness in [1, 6, 7])
- Then life := group.aliveness
- Else Begin
- defending := first_player = group.owner;
- found_one := false;
- points_to_consider := critical_points_for (group, board);
- Find_Member (group, where);
- move := points_to_consider;
- Save_Board (board, previous_board_file);
- While move <> nil do
- If found_one or suicidal (move^.location.x, move^.location.y, first_player, board)
- Then Begin
- If defending
- Then move^.value := 0
- Else move^.value := 8;
- move := move^.next
- End
- Else Begin
- Play_At (move^.location.x, move^.location.y, first_player, board, depth + 1);
- If board.data [where.x, where.y].group = 0
- Then move^.value := 0
- Else If plenty_of_time (physical_board.turn)
- Then move^.value := life (board.groups [board.data [where.x, where.y].group]^,
- opposite (first_player), board, depth + 1, crucial_move)
- Else move^.value := life (board.groups [board.data [where.x, where.y].group]^,
- opposite (first_player), board, depth + 2, crucial_move);
- If (defending and (move^.value = 7))
- or ((not defending) and (move^.value = 1))
- Then found_one := true;
- move := move^.next;
- Load_Board (board, previous_board_file)
- End;
- Delete_Board (previous_board_file);
- If defending
- Then best := 0
- Else best := 8;
- move := points_to_consider;
- While move <> nil do
- Begin
- If defending
- Then Begin
- If move^.value > best
- Then Begin
- best := move^.value;
- crucial_move := move^.location
- End
- End
- Else If move^.value < best
- Then Begin
- best := move^.value;
- crucial_move := move^.location
- End;
- move := move^.next
- End;
- Clean_Out_Location_List (points_to_consider);
- life := best
- End
- End; {Life}
-
-
- PROCEDURE Update_Aliveness (var group: group_type; var board: board_type; var depth: byte);
- {Rates the group's life from 1 (hosed) to 7 (2 eyes)}
- Var
- opponent_first, self_first: byte;
- Begin {Update Aliveness}
- With group do
- If aliveness < 7 {It's not already unconditionally alive}
- Then Begin
- aliveness := aji (group, board);
- If aliveness = 4 {Unsettled}
- Then If alive (group, board, depth)
- Then aliveness := 7 {Alive}
- Else If (depth = 0) and (plenty_of_time (board.turn) or (group.last_update = 0))
- {Only read sequences from physical board, and when there's time}
- Then Begin
- opponent_first := life (group, opposite (group.owner), board, depth, group.to_kill);
- If opponent_first > 5
- Then aliveness := 5 {Alive if opponent moves first}
- Else Begin
- self_first := life (group, group.owner, board, depth, group.to_save);
- If opponent_first < 3
- Then aliveness := aliveness - 1;
- If self_first > 5
- Then aliveness := aliveness + 1
- End
- End
- End
- End; {Update Aliveness}
-
-
- FUNCTION Attack_Potential_At (var x, y: byte; var group: group_type; var board: board_type): integer;
- {Returns an integer rating of the point as a place to attack the group}
- {LOCAL}
- Var
- direction: direction_type;
- neighbor: location_type;
- importance, tally: integer;
- Begin {Attack Potential At}
- If legal_move (x, y, opposite (group.owner), board)
- Then Begin
- If (group.interpretation [x, y].adjacent) and (board.data [x, y].color = empty)
- Then tally := 5000
- Else tally := 0;
- For direction := north to ne do
- Begin
- neighbor := neighbors [x, y, direction];
- If on_board (neighbor.x, neighbor.y)
- Then Begin
- importance := 0;
- With group.interpretation [neighbor.x, neighbor.y] do
- If connected or adjacent
- Then If board.data [neighbor.x, neighbor.y].color = group.owner
- Then importance := 10
- Else If adjacent and (board.data [neighbor.x, neighbor.y].color = empty)
- Then importance := 5
- Else importance := 2;
- If direction in [north..west]
- Then importance := importance * 141
- Else importance := importance * 100;
- tally := tally + importance
- End
- End;
- attack_potential_at := tally
- End
- Else attack_potential_at := 0
- End; {Attack Potential At}
-
-
- FUNCTION Critical_Points_For (var group: group_type; var board: board_type): location_list;
- {Generates a list of points to consider in attacking/defending the group}
- Var
- x, y: byte;
- rating: integer_board;
- Begin {Critical Points For}
- For x := 1 to board_size do
- For y := 1 to board_size do
- rating [x, y] := Attack_Potential_At (x, y, group, board);
- critical_points_for := highest_rated (rating)
- End; {Critical Points For}
-
-
- FUNCTION Strategy (var board: board_type; var player: color_type): string;
- {Returns a string suggesting territory, defense, or offense}
- Var
- b, w: integer;
- count: byte;
- points: integer_board;
- i_have_a_weak_group, he_has_a_weak_group: boolean;
- Begin {Strategy}
- Estimate_Score (board, b, w, points);
- If b + w < board_size_squared div 2
- Then strategy := 'Grab for some of that empty territory'
- Else Begin
- i_have_a_weak_group := false;
- he_has_a_weak_group := false;
- count := 1;
- Repeat
- If board.groups [count]^.aliveness in [3..5]
- Then If board.groups [count]^.owner = player
- Then i_have_a_weak_group := true
- Else he_has_a_weak_group := true;
- Inc (count)
- Until (board.groups [count]^.size = 0) or (i_have_a_weak_group and he_has_a_weak_group);
- If abs (w - b) < board_size
- Then If i_have_a_weak_group
- Then strategy := 'Defend your groups'
- Else If he_has_a_weak_group
- Then strategy := 'Attack the opponent''s groups'
- Else strategy := 'It''s close--push'
- Else If ((w > b) and (player = black_stone)) or ((b > w) and (player = white_stone))
- Then If he_has_a_weak_group
- Then strategy := 'You need to kill something'
- Else strategy := 'You''re in trouble--invade'
- Else If i_have_a_weak_group
- Then strategy := 'Keep your lead--defend your groups'
- Else strategy := 'You''re winning--seal it up'
- End
- End; {Strategy}
-
-
- PROCEDURE Pick_Move (var board: board_type; player: color_type; var move: location_list; depth: byte);
- {Picks a strategy and calls the appropriate tacticians}
- Var
- count: byte;
- b, w: integer;
- points: integer_board;
- i_have_a_weak_group, he_has_a_weak_group: boolean;
- Begin {Pick Move}
- move := nil;
- Estimate_Score (board, b, w, points);
- If b + w < board_size_squared div 2
- Then Grab_Territory (board, player, move, points, depth)
- Else Begin
- i_have_a_weak_group := false;
- he_has_a_weak_group := false;
- count := 1;
- Repeat
- If board.groups [count]^.aliveness in [4..5]
- Then If board.groups [count]^.owner = player
- Then i_have_a_weak_group := true
- Else he_has_a_weak_group := true;
- Inc (count)
- Until (board.groups [count]^.size = 0) or (i_have_a_weak_group and he_has_a_weak_group);
- If abs (w - b) < board_size
- Then If i_have_a_weak_group
- Then Defend_Groups (board, player, move, depth)
- Else If he_has_a_weak_group
- Then Attack_Opponent (board, player, move, depth)
- Else Push (board, player, move, points, depth)
- Else If ((w > b) and (player = black_stone)) or ((b > w) and (player = white_stone))
- Then If he_has_a_weak_group
- Then Attack_Opponent (board, player, move, depth)
- Else Invade (board, player, move, points, depth)
- Else If i_have_a_weak_group
- Then Defend_Groups (board, player, move, depth)
- End;
- If move = nil
- Then Seal_Up (board, player, move, points, depth);
- If move = nil
- Then Push (board, player, move, points, depth);
- If (move = nil) and (i_have_a_weak_group)
- Then Defend_Groups (board, player, move, depth);
- If (move = nil) and (he_has_a_weak_group)
- Then Attack_Opponent (board, player, move, depth);
- If move = nil
- Then Invade (board, player, move, points, depth);
- If move = nil
- Then Begin
- New (move);
- move^.location.x := 0;
- move^.location.y := 0;
- move^.next := nil
- End
- End; {Pick Move}
-
-
- PROCEDURE Play_Move (var board: board_type; player: color_type; depth: byte);
- {Calls pick_move and plays it}
- Var
- move, good_moves: location_list;
- start_hour, start_minute, start_second, start_hundredths, hour, minute, second, hundredths: word;
- move_coordinates: string;
- Begin {Play Move}
- WriteLn ('I have ', time_left div 60, ' minutes left');
- GetTime (start_hour, start_minute, start_second, start_hundredths);
- Pick_Move (board, player, good_moves, depth);
- move := good_moves;
- While move^.next <> nil do
- move := move^.next;
- If on_board (move^.location.x, move^.location.y)
- Then Begin
- Str (move^.location.y, move_coordinates);
- Case move^.location.x of
- 1..8: move_coordinates := chr (move^.location.x + ord ('a') - 1) + move_coordinates;
- 9..19: move_coordinates := chr (move^.location.x + ord ('a')) + move_coordinates
- End;
- WriteLn;
- WriteLn ('*** I move at ', move_coordinates, ' ***');
- Play_At (move^.location.x, move^.location.y, player, physical_board, 0)
- End
- Else If move^.location.y = 0
- Then Begin
- WriteLn;
- WriteLn ('*** I Pass ***');
- Pass (board)
- End
- Else Get_User_Move (player, board);
- WriteLn ('(Return to continue)');
- ReadLn;
- Clean_Out_Location_List (good_moves);
- GetTime (hour, minute, second, hundredths);
- If hour > start_hour
- Then Dec (time_left, (hour - start_hour) * 3600)
- Else If start_hour > hour
- Then Dec (time_left, (24 + hour - start_hour) * 3600);
- If minute > start_minute
- Then Dec (time_left, (minute - start_minute) * 60)
- Else If start_minute > minute
- Then Inc (time_left, (start_minute - minute) * 60);
- If second > start_second
- Then Dec (time_left, second - start_second)
- Else If start_second > second
- Then Inc (time_left, start_second - second);
- If (time_left > total_time) or (time_left = 0)
- Then time_left := 1
- End; {Play Move}
-
-
- END. {Sequence}
-
- SHAR_EOF
- fi
- if test -f 'tactics.pas'
- then
- echo shar: "will not over-write existing file 'tactics.pas'"
- else
- cat << \SHAR_EOF > 'tactics.pas'
- UNIT Tactics;
- {Tactical routines for Fumiko}
-
-
- INTERFACE
-
-
- USES
- GloblCTV, Arcana, GroupMgr, BrdHndlr;
-
-
- PROCEDURE Grab_Territory (var board: board_type; player: color_type; var move: location_list;
- var weights: integer_board; depth: byte);
- {Plays in wide-open areas}
-
-
- PROCEDURE Defend_Groups (var board: board_type; player: color_type; var move: location_list; depth: byte);
- {Defends weak groups}
-
-
- PROCEDURE Attack_Opponent (var board: board_type; player: color_type; var move: location_list; depth: byte);
- {Attacks weak groups}
-
-
- PROCEDURE Push (var board: board_type; player: color_type; var move: location_list; var weights: integer_board; depth: byte);
- {Plays on opponent's side of borders}
-
-
- PROCEDURE Invade (var board: board_type; player: color_type; var move: location_list;
- var weights: integer_board; depth: byte);
- {Plays inside loose enemy territory}
-
-
- PROCEDURE Seal_Up (var board: board_type; player: color_type; var move: location_list;
- var weights: integer_board; depth: byte);
- {Plays on own side of borders}
-
-
- IMPLEMENTATION
-
-
- USES
- Sequence;
-
-
- FUNCTION Border (var x, y: byte; var weights: integer_board): boolean;
- {Returns true if the space is on the edge of an influence area. LOCAL}
- Var
- direction: direction_type;
- neighbor: location_type;
- positive, negative: boolean;
- Begin {Border}
- positive := false;
- negative := false;
- For direction := north to west do
- Begin
- neighbor := neighbors [x, y, direction];
- If on_board (neighbor.x, neighbor.y)
- Then Case weights [neighbor.x, neighbor.y] of
- (-maxint - 1)..-1: negative := true;
- 0: Begin
- positive := true;
- negative := true
- End;
- 1..maxint: positive := true
- End
- End;
- border := negative and positive
- End; {Border}
-
-
- PROCEDURE Grab_Territory (var board: board_type; player: color_type; var move: location_list;
- var weights: integer_board; depth: byte);
- {Plays in wide-open areas}
- Var
- x, y: byte;
- Begin {Grab Territory}
- WriteLn ('I''l take some of that territory...');
- For x := 1 to board_size do
- For y := 1 to board_size do
- If (weights [x, y] = 0) and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
- Then weights [x, y] := 10000 + terrain [x, y]
- Else weights [x, y] := (10000 div abs (weights [x, y])) + terrain [x, y];
- move := highest_rated (weights)
- End; {Grab Territory}
-
- PROCEDURE Defend_Groups (var board: board_type; player: color_type; var move: location_list; depth: byte);
- {Defends weak groups}
- Var
- value: integer_board;
- count, garbage: byte;
- Begin {Defend Groups}
- WriteLn ('I''m a little nervous about these guys');
- value := zero_board;
- count := 1;
- While board.groups [count]^.size <> 0 do
- With board.groups [count]^ do
- Begin
- If (owner = player) and (aliveness in [3..5])
- Then If on_board (to_save.x, to_save.y) and legal_move (to_save.x, to_save.y, player, board)
- and not suicidal (to_save.x, to_save.y, player, board)
- Then Inc (value [to_save.x, to_save.y], size)
- Else Begin
- garbage := Life (board.groups [count]^, player, board, depth, to_save);
- If on_board (to_save.x, to_save.y)
- Then Inc (value [to_save.x, to_save.y], size)
- End;
- Inc (count)
- End;
- move := highest_rated (value)
- End; {Defend Groups}
-
-
- PROCEDURE Attack_Opponent (var board: board_type; player: color_type; var move: location_list; depth: byte);
- {Attacks weak groups}
- Var
- value: integer_board;
- count, garbage: byte;
- Begin {Attack Opponent}
- WriteLn ('Die, Pink boy!');
- value := zero_board;
- count := 1;
- While board.groups [count]^.size <> 0 do
- With board.groups [count]^ do
- Begin
- If (owner = opposite (player)) and (aliveness in [3..5])
- Then If on_board (to_kill.x, to_kill.y) and legal_move (to_kill.x, to_kill.y, player, board)
- and not suicidal (to_kill.x, to_kill.y, player, board)
- Then Inc (value [to_kill.x, to_kill.y], size)
- Else Begin
- garbage := Life (board.groups [count]^, player, board, depth, to_kill);
- If on_board (to_kill.x, to_kill.y)
- Then Inc (value [to_kill.x, to_kill.y], size)
- End;
- Inc (count)
- End;
- move := highest_rated (value)
- End; {Attack Opponent}
-
-
- PROCEDURE Push (var board: board_type; player: color_type; var move: location_list; var weights: integer_board; depth: byte);
- {Plays on opponent's side of borders}
- Var
- x, y: byte;
- value: integer_board;
- Begin {Push}
- WriteLn ('I just need a few more points...');
- If player = white_stone
- Then For x := 1 to board_size do
- For y := 1 to board_size do
- weights [x, y] := - weights [x, y];
- For x := 1 to board_size do
- For y := 1 to board_size do
- If (weights [x, y] < 0) and border (x, y, weights)
- and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
- Then value [x, y] := terrain [x, y] + weights [x, y]
- Else value [x, y] := 0;
- move := highest_rated (value)
- End; {Push}
-
-
- PROCEDURE Invade (var board: board_type; player: color_type; var move: location_list;
- var weights: integer_board; depth: byte);
- {Plays inside loose enemy territory}
- Var
- x, y: byte;
- value: integer_board;
- Begin {Invade}
- WriteLn ('Geronimo!');
- If player = white_stone
- Then For x := 1 to board_size do
- For y := 1 to board_size do
- weights [x, y] := - weights [x, y];
- For x := 1 to board_size do
- For y := 1 to board_size do
- If (weights [x, y] < 0) and maneuvering_room (x, y, player, board)
- and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
- Then value [x, y] := terrain [x, y] + weights [x, y]
- Else value [x, y] := 0;
- move := highest_rated (value)
- End; {Invade}
-
-
- PROCEDURE Seal_Up (var board: board_type; player: color_type; var move: location_list;
- var weights: integer_board; depth: byte);
- {Plays on own side of borders}
- Var
- x, y: byte;
- value: integer_board;
- Begin {Seal Up}
- WriteLn ('I don''t have to be greedy.');
- If player = white_stone
- Then For x := 1 to board_size do
- For y := 1 to board_size do
- weights [x, y] := - weights [x, y];
- For x := 1 to board_size do
- For y := 1 to board_size do
- If (weights [x, y] > 0) and border (x, y, weights)
- and legal_move (x, y, player, board) and not suicidal (x, y, player, board)
- Then value [x, y] := (maxint - weights [x, y]) + terrain [x, y]
- Else value [x, y] := 0;
- move := highest_rated (value)
- End; {Seal Up}
-
-
- END. {Tactics}
- SHAR_EOF
- fi
- if test -f 'trickle.fum'
- then
- echo shar: "will not over-write existing file 'trickle.fum'"
- else
- cat << \SHAR_EOF > 'trickle.fum'
- +1
- 3
- 3
-
- SHAR_EOF
- fi
- exit 0
- # End of shell archive
-